using System;
using System.IO;
using System.Net;
using System.Xml;
using DeployReports.Types;
using Microsoft.SqlServer.ReportingServices2005;
using NAnt.Core;
using NAnt.Core.Attributes;
using Task = NAnt.Core.Task;

namespace DeployReports.Tasks
{
	[TaskName("deployReportingServices")]
	public class DeployReportsTask : Task
	{
		private ReportFileSet reports;
		private DataSourceElementCollection dataSources;
		private string serverUrl;

		[StringValidator(AllowEmpty = false)]
		[TaskAttribute("serverUrl", Required = true)]
		public string ServerUrl
		{
			get { return serverUrl; }
			set { serverUrl = value; }
		}

		[BuildElement("dataSources", Required = false)]
		public DataSourceElementCollection DataSources
		{
			get { return dataSources; }
			set { dataSources = value; }
		}

		[BuildElement("reports", Required = true)]
		public ReportFileSet Reports
		{
			get { return reports; }
			set { reports = value; }
		}

		protected override void ExecuteTask()
		{
			ReportingService2005 rs = new ReportingService2005();
			rs.Url = ServerUrl;
			rs.Credentials = CredentialCache.DefaultCredentials;

			CreateFolderIfNotExists(DataSources.PathOnServer.Replace("/", ""), rs);
			CreateFolderIfNotExists(Reports.PathOnServer.Replace("/", ""), rs);

			foreach (DataSourceElement dataSource in DataSources.Definition)
			{
				DeployDataSource(dataSource, rs);
			}

			foreach (string fileName in Reports.FileNames)
			{
				DeployReport(fileName, rs);
			}
		}

		private void DeployDataSource(DataSourceElement element, ReportingService2005 rs)
		{
			string dataSourceName = element.DataSourceName;
			DataSourceDefinition def = element.GetDataSourceDefinition();

			bool doCreate = true;
			CatalogItem dataSourceItem = FindOne(dataSources.PathOnServer, dataSourceName, rs);
			if (dataSourceItem!=null)
			{
				if (!dataSources.Overwrite)
				{
					doCreate = false;
				}
				else
				{
					Delete(dataSources.PathOnServer, dataSourceName, rs,dataSourceItem.Type);
				}
			}
			if (doCreate)
			{
				Log(Level.Info, "Creating data source {0}.", dataSourceName);
				rs.CreateDataSource(dataSourceName, DataSources.PathOnServer, false, def, null);
			}
		}
		private void DeployReport(string fileName, ReportingService2005 rs)
		{
			FileInfo fileInfo = new FileInfo(fileName);
			string reportName = fileInfo.Name.Replace(fileInfo.Extension, "");
			Log(Level.Info, "Deploying report " + reportName);
			byte[] definition = GetReportDefinitionBytes(fileInfo);

			bool doCreate = true;
			CatalogItem reportItem = FindOne(Reports.PathOnServer, reportName, rs);
			if (reportItem!=null)
			{
				if (!Reports.Overwrite)
				{
					doCreate = false;
				}
				else
				{
					rs.DeleteItem(reportItem.Path);
//					Delete(Reports.PathOnServer, reportName, rs, reportItem.Type);
				}
			}
			if (doCreate)
			{
				Log(Level.Info, "Creating data source {0}.", reportName);
				Warning[] warnings = 
					rs.CreateReport(reportName, Reports.PathOnServer, Reports.Overwrite, definition, null);
				LogAnyWarnings(fileInfo, warnings);
			}
			string reportFullPath = Reports.PathOnServer + (Reports.PathOnServer.EndsWith("/") ? "" : "/") +
									reportName;
			FixDataSourceReferences(reportFullPath, rs);
		}

		private void Delete(string folderName, string itemName, ReportingService2005 rs, ItemTypeEnum itemTypeEnum)
		{
			Log(Level.Info, "Deleting {1} '{0}' so it can be overwritten.", itemName, itemTypeEnum.ToString());
			rs.DeleteItem(folderName + (folderName.EndsWith("/") ? "" : "/") + itemName);
		}

		private void FixDataSourceReferences(string reportFullPath, ReportingService2005 rs)
		{
			DataSource[] sources = rs.GetItemDataSources(reportFullPath);
			DataSource[] newSources = new DataSource[sources.Length];
			for (int i = 0; i < newSources.Length; i++)
			{
				newSources[i] = new DataSource();
				DataSourceReference reference =
					new DataSourceReference();
				string dataSourceFullPath = DataSources.PathOnServer +
											(DataSources.PathOnServer.EndsWith("/") ? "" : "/") + sources[i].Name;
				//                    DataSourceDefinition dataSourceDefinition = rs.GetDataSourceContents(dataSourceFullPath);
				reference.Reference = dataSourceFullPath;
				newSources[i].Item =
					reference;
				newSources[i].Name = sources[i].Name;
			}


			rs.SetItemDataSources(reportFullPath, newSources);
		}

		private void CreateFolderIfNotExists(string folderName, ReportingService2005 rs)
		{
			if (string.IsNullOrEmpty(folderName))
			{
				Log(Level.Info, "Not going to try to create the root folder.");
			}

			if (FindOne("/", folderName, rs)==null)
			{
				Log(Level.Info, "Creating folder {0}.", folderName);
				rs.CreateFolder(folderName, "/", null);
				Log(Level.Debug, "Done creating folder {0}.", folderName);
			}
			else
			{
				Log(Level.Info, "Not creating folder {0} because it already exists.", folderName);
			}
		}

		private CatalogItem FindOne(string folderName, string itemName, ReportingService2005 rs)
		{
			SearchCondition condition = new SearchCondition();
			condition.Condition = ConditionEnum.Equals;
			condition.ConditionSpecified = true;
			condition.Name = "Name";
			condition.Value = itemName;
			CatalogItem[] items = rs.FindItems(folderName, BooleanOperatorEnum.And, new SearchCondition[] { condition });
			if (items.Length > 0)
			{
				CatalogItem theItem = items[0];
				return theItem;

			}
			else
			{
				Log(Level.Info, "'{0}/{1} doesn't exist.", folderName, itemName);
				return null;
			}			
		}

		/// <summary>
		/// Return true if either: (A) The CatalogItem's type matches the <paramref name="type">type</paramref> parameter.
		///  or (B) If <paramref name="type">type</paramref> is ItemTypeEnum.LinkedReport or ItemTypeEnum.Report, then 
		///  match <paramref name="theItem">theItem</paramref>.Type on either of those values.
		/// </summary>
		/// <param name="theItem"></param>
		/// <param name="type"></param>
		/// <returns></returns>
		private static bool ItemIsType(CatalogItem theItem, ItemTypeEnum type)
		{
			return theItem.Type == type ||
				   ((theItem.Type == ItemTypeEnum.Report || theItem.Type == ItemTypeEnum.LinkedReport)
					&& (type == ItemTypeEnum.Report || type == ItemTypeEnum.LinkedReport));
		}


		private static byte[] GetReportDefinitionBytes(FileInfo fileInfo)
		{
			byte[] definition;
			using (FileStream stream = fileInfo.OpenRead())
			{
				definition = new byte[stream.Length];
				stream.Read(definition, 0, definition.Length);
			}
			return definition;
		}

		private void LogAnyWarnings(FileInfo fileInfo, Warning[] warnings)
		{
			if (null != warnings)
				foreach (Warning warning in warnings)
				{
					if (!warning.Code.Equals("rsDataSourceReferenceNotPublished", StringComparison.InvariantCultureIgnoreCase))
					{
						Log(Level.Debug,
							CREATE_REPORT_WARNING_STRING,
							ServerUrl, fileInfo.Name, warning.Message, warning.Code, warning.ObjectName,
							warning.ObjectType, warning.Severity);
					}
				}
		}

		private const string CREATE_REPORT_WARNING_STRING =
			@"Received warning from server '{0}', uploading file '{1}':
    Message:     {2}
    Code:        {3}
    Object Name  {4} 
    Object Type  {5}
    Severity     {6}
";
	}
}